/* PROJECT:         Lite sndrec32
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            base/applications/sndrec32/audio_waveout.cpp
 * PURPOSE:         Sound recording
 * PROGRAMMERS:     Marco Pagliaricci (irc: rendar)
 */



#include "stdafx.h"
#include "audio_waveout.hpp"


_AUDIO_NAMESPACE_START_



void
audio_waveout::init_( void )
{

    ZeroMemory(( LPVOID ) &wave_format, 
                    sizeof( WAVEFORMATEX ));

    wave_format.cbSize = sizeof( WAVEFORMATEX );

    waveout_handle = 0;

    playthread_id = 0;
    wakeup_playthread = 0;

    buf_secs = _AUDIO_DEFAULT_WAVEOUTBUFSECS;


    status = WAVEOUT_NOTREADY;

}




void 
audio_waveout::alloc_buffers_mem_( unsigned int buffs, float secs )
{


    unsigned int 
        onebuf_size = 0, tot_size = 0;


    //
    // Release old memory
    //

    if ( main_buffer )
        delete[] main_buffer;


    if ( wave_headers )
        delete[] wave_headers;



    //
    // Calcs size of the buffers
    //

    onebuf_size = ( unsigned int )
        (( float )aud_info.byte_rate() * secs ); 


    tot_size = onebuf_size * buffs;

    
    
    
    //
    // Allocs memory for the audio buffers
    //

    main_buffer = new BYTE [ tot_size ];



    //
    // Allocs memory for the `WAVEHDR' structures.
    //

    wave_headers = ( WAVEHDR * ) 
        new BYTE [ sizeof( WAVEHDR ) * buffs ];



    //
    // Zeros memory.
    //

    ZeroMemory( main_buffer, tot_size );

    ZeroMemory( wave_headers, 
        sizeof( WAVEHDR ) * buffs );


    //
    // Updates total size of the buffers.
    //

    mb_size = tot_size;
}


void 
audio_waveout::init_headers_( void )
{



    //
    // If there is no memory for memory or
    // headers, simply return.
    //

    if (( !wave_headers ) || ( !main_buffer ))
        return;


    //
    // This is the size for one buffer
    //

    DWORD buf_sz = mb_size / buffers;



    //
    // This is the base address for one buffer
    //
    
    BYTE * buf_addr = main_buffer;



    ZeroMemory( wave_headers, sizeof( WAVEHDR ) * buffers );


    //
    // Initializes headers.
    //

    for ( unsigned int i = 0; i < buffers; ++i )
    {
        
        //
        // Sets the correct base address and 
        // length for the little buffer.
        //

        wave_headers[ i ].dwBufferLength = mb_size / buffers;
        wave_headers[ i ].lpData = ( LPSTR ) buf_addr;

        //
        // Unsets the WHDR_DONE flag.
        //

        wave_headers[ i ].dwFlags &= ~WHDR_DONE;


        
        //
        // Sets the WAVEHDR user data with an
        // unique little buffer ID#
        //

        wave_headers[ i ].dwUser = ( unsigned int ) i;

        
        
        //
        // Increments little buffer base address.
        //

        buf_addr += buf_sz;
    }

}


void 
audio_waveout::prep_headers_( void )
{
    MMRESULT err;
    bool error = false;


    //
    // If there is no memory for memory or
    // headers, throw error.
    //

    if (( !wave_headers ) 
        || ( !main_buffer ) || ( !waveout_handle ))
    {} //TODO: throw error!



    for ( unsigned int i = 0; i < buffers; ++i )
    {
        err = waveOutPrepareHeader( waveout_handle, 
                    &wave_headers[ i ], sizeof( WAVEHDR ));


        if ( err != MMSYSERR_NOERROR )
            error = true;

    }
    

    if ( error )
    {} //TODO: throw error indicating which
      //header i-th is errorneous



}

void 
audio_waveout::unprep_headers_( void )
{
    MMRESULT err;
    bool error = false;



    //
    // If there is no memory for memory or
    // headers, throw error.
    //

    if (( !wave_headers ) 
        || ( !main_buffer ) || ( !waveout_handle ))
    {} //TODO: throw error!



    for ( unsigned int i = 0; i < buffers; ++i )
    {
        err = waveOutUnprepareHeader( waveout_handle, 
                    &wave_headers[ i ], sizeof( WAVEHDR ));


        if ( err != MMSYSERR_NOERROR )
            error = true;

    }
    

    if ( error )
    {} //TODO: throw error indicating which
      //header i-th is errorneous

}









void 
audio_waveout::free_buffers_mem_( void )
{



    //
    // Frees memory
    //

    if ( main_buffer )
        delete[] main_buffer;


    if ( wave_headers )
        delete[] wave_headers;


    main_buffer = 0;
    wave_headers = 0;




}














void 
audio_waveout::open( void )
{

    MMRESULT err;
    HANDLE playthread_handle = 0;


    //
    // Checkin the status of the object
    //

    if ( status != WAVEOUT_NOTREADY )
    {} //TODO: throw error


    //
    // Creating the EVENT object that will be signaled
    // when the playing thread has to wake up.
    //

    wakeup_playthread = 
        CreateEvent( 0, FALSE, FALSE, 0 );

    if ( !wakeup_playthread )
    {


        status = WAVEOUT_ERR;

        //TODO: throw error
    }



    //
    // Inialize buffers for recording audio 
    // data from the wavein audio line.
    //

    alloc_buffers_mem_( buffers, buf_secs );
    init_headers_();






    //
    // Sound format that will be captured by wavein
    //

    wave_format.wFormatTag = WAVE_FORMAT_PCM;

    wave_format.nChannels = aud_info.channels();
    wave_format.nSamplesPerSec = aud_info.sample_rate();
    wave_format.wBitsPerSample = aud_info.bits();
    wave_format.nBlockAlign = aud_info.block_align();
    wave_format.nAvgBytesPerSec = aud_info.byte_rate();



    //
    // Creating the recording thread
    //

    playthread_handle = 
        CreateThread( NULL, 
                      0, 
                      audio_waveout::playing_procedure, 
                      ( PVOID ) this, 
                      0, 
                      &playthread_id 
            );

    

    //
    // Checking thread handle
    //

    if ( !playthread_handle )
    {

        //
        // Updating status
        //

        status = WAVEOUT_ERR;		
        //TODO: throw error

    }


    //
    // We don't need the thread handle anymore,
    // so we can close it from now. (We'll just
    // need the thread ID for the `waveInOpen' API)
    //

    CloseHandle( playthread_handle );



    //
    // Reset the `audio_source' to the start
    // position.
    //

    audio_buf.set_position_start();




    //
    // Opens the WAVE_OUT device.
    //

    err = waveOutOpen( 
                &waveout_handle, 
                WAVE_MAPPER, 
                &wave_format, 
                playthread_id, 
                0, 
                CALLBACK_THREAD | WAVE_ALLOWSYNC 
            );



    if ( err != MMSYSERR_NOERROR )
    {
        MessageBox(0, _T("waveOutOpen Error"), 0, 0);
        //TODO: throw error

    }




    status = WAVEOUT_READY;


}



void 
audio_waveout::play( void )
{


    MMRESULT err;
    unsigned int i;

    if ( !main_buffer )
    { return; } //TODO; throw error, or assert




    //
    // If the status is PAUSED, we have to
    // resume the audio playing.
    //
    if ( status == WAVEOUT_PAUSED )
    {

        //
        // Updates status.
        //

        status = WAVEOUT_PLAYING;


        //
        // Tells to the driver to resume
        // audio playing.
        //

        waveOutRestart( waveout_handle );
        

        //
        // Wakeup playing thread.
        //

        SetEvent( wakeup_playthread );

        return;

    } //if status == WAVEOUT_PAUSED





    if ( status != WAVEOUT_READY ) 
        return;
    



    //
    // Prepares WAVEHDR structures.
    //

    prep_headers_();



    //
    // Sets correct status.
    //

    status = WAVEOUT_PLAYING;
    
    

    //
    // Reads the audio from the start.
    //

    //audio_buf.set_position_start();

    

    
    //
    // Reads the first N bytes from the audio
    // buffer, where N = the total size of all
    // little buffers.
    //

    audio_buf.read( main_buffer, mb_size );

    



    
    //
    // Wakeup the playing thread.
    //

    SetEvent( wakeup_playthread );




    //
    // Sends all the little buffers to the
    // audio driver, so it can play the sound 
    // data.
    //

    for ( i = 0; i < buffers; ++ i )
    {

        
        err = waveOutWrite( waveout_handle, &wave_headers[ i ], sizeof( WAVEHDR ));

        if ( err != MMSYSERR_NOERROR )
        {
        

            MessageBox(0, _T("waveOutWrite Error"), 0, 0);
                                
            //TODO: throw error
        }

    }

}


void 
audio_waveout::pause( void )
{

    MMRESULT err;


    //
    // If the waveout object is not playing audio,
    // do nothing.
    //
    
    if ( status == WAVEOUT_PLAYING )
    {

        //
        // Updating status.
        //

        status = WAVEOUT_PAUSED;


        //
        // Tells to audio driver to pause audio.
        //

        err = waveOutPause( waveout_handle );


        if ( err != MMSYSERR_NOERROR )
        {

            MessageBox(0, _T("waveOutPause Error"), 0, 0);
            //TODO: throw error

        }

    }

}


void 
audio_waveout::stop( void )
{

    MMRESULT err;


    //
    // Checks the current status
    //

    if (( status != WAVEOUT_PLAYING ) 
            && ( status != WAVEOUT_FLUSHING )
            && ( status != WAVEOUT_PAUSED ))
    {
        //
        // Do nothing.
        //

        return;

    }



    //
    // Sets a new status
    //

    status = WAVEOUT_STOP;



    //
    // Flushes pending audio datas
    //

    err = waveOutReset( waveout_handle );



    if ( err != MMSYSERR_NOERROR )
    {

        MessageBox(0, _T("err WaveOutReset.\n"),_T("ERROR"), 0);
        //TODO: throw error

    }



    //
    // Sets the start position of the audio
    // buffer.
    //

    audio_buf.set_position_start();


    //
    // Cleans little buffers.
    //

    unprep_headers_();
    init_headers_();


    
    //
    // Refreshes the status.
    //

    status = WAVEOUT_READY;

}

void
audio_waveout::close( void )
{

    MMRESULT err;


    //
    // If the `wave_out' object is playing audio,
    // or it is in paused state, we have to call
    // the `stop' member function, to flush 
    // pending buffers.
    //
    
    if (( status == WAVEOUT_PLAYING ) 
                    || ( status== WAVEOUT_PAUSED ))
    {
    
        stop();

    }



    //
    // When we have flushed all pending buffers,
    // the wave out handle can be successfully closed.
    //

    err = waveOutClose( waveout_handle );


    if ( err != MMSYSERR_NOERROR )
    {

        MessageBox(0, _T("waveOutClose Error"), 0, 0);
        //TODO: throw error

    }

    free_buffers_mem_();

}


DWORD WINAPI 
audio_waveout::playing_procedure( LPVOID arg )
{
    MSG msg;
    WAVEHDR * phdr;
    MMRESULT err;
    audio_waveout * _this = ( audio_waveout * ) arg;
    unsigned int read_size;
    


    //
    // Check the arg pointer
    //

    if ( _this == 0 )
        return 0;

    
    
    //
    // The thread can go to sleep for now.
    // It will be wake up only when there is audio data
    // to be recorded.
    //

    if ( _this->wakeup_playthread )
        WaitForSingleObject(_this->wakeup_playthread, INFINITE);



    //
    // Entering main polling loop
    //

    while ( GetMessage( &msg, 0, 0, 0 )) 	
    {	

        switch ( msg.message )
        {

            case MM_WOM_DONE:
            
                phdr = ( WAVEHDR * ) msg.lParam;


                //
                // If the status of the `wave_out' object
                // is different than playing, then the thread
                // can go to sleep.
                //

                if (( _this->status != WAVEOUT_PLAYING ) && 
                        ( _this->status != WAVEOUT_FLUSHING ) &&
                                    ( _this->wakeup_playthread ))
                {
                    WaitForSingleObject(_this->wakeup_playthread, INFINITE);
                }



                //
                // The playing thread doesn't have to sleep,
                // so let's checking first if the little buffer
                // has been sent to the audio driver (it has the 
                // WHDR_DONE flag). If it is, we can read new
                // audio datas from the `audio_producer' object,
                // refill the little buffer, and resend it to the
                // driver with waveOutWrite( ) API.
                //
                
                if ( phdr->dwFlags & WHDR_DONE )
                {

                    if ( _this->status == WAVEOUT_PLAYING )
                    {

                        //
                        // Here the thread is still playing a sound,
                        // so it can read new audio data from the
                        // `audio_producer' object.
                        //
                    
                        read_size = 
                            _this->audio_buf.read(
                                            ( BYTE * ) phdr->lpData, 
                                            phdr->dwBufferLength 
                                        );

                    } else
                       read_size = 0;

                    

                    //
                    // If the `audio_producer' object, has produced some
                    // audio data, so `read_size' will be > 0.
                    //
                        
                    if ( read_size )
                    {

                        //
                        // Adjusts the correct effectively read size.
                        //

                        phdr->dwBufferLength = read_size;

                        //
                        // Before sending the little buffer to the
                        // driver, we have to remove the `WHDR_DONE'
                        // flag, because the little buffer now contain
                        // new audio data that have to be played.
                        //

                        phdr->dwFlags &= ~WHDR_DONE;


                        //
                        // Plays the sound of the little buffer.
                        //

                        err = waveOutWrite( 
                                    _this->waveout_handle, 
                                    phdr, 
                                    sizeof( WAVEHDR )
                                );


                        //
                        // Checking if any error has occured.
                        //

                        if ( err != MMSYSERR_NOERROR )
                        {
                                MessageBox(0, _T("waveOutWrite Error"), 0, 0);
                                //TODO: throw error
                        }




                    } else { // if ( read_size )



                        //
                        // Here `read_size' is 0, so the
                        // `audio_producer' object, doesn't have any
                        // sound data to produce anymore.
                        // So, now we have to see the little buffer
                        // #ID to establishing what to do.
                        //

                        if ( phdr->dwUser == 0 )
                        {

                           
                            
                            //
                            // Here `read_size' is 0, and the buffer
                            // user data contain 0, so this is the
                            // first of N little buffers that came
                            // back with `WHDR_DONE' flag; this means
                            // that this is the last little buffer in
                            // which we have to read data to; so we
                            // can _STOP_ reading data from the 
                            // `audio_producer' object: doing this is
                            // accomplished just setting the current
                            // status as "WAVEOUT_FLUSHING".
                            //

                            _this->status = WAVEOUT_FLUSHING;


                        } else if ( phdr->dwUser == ( _this->buffers - 1 )) {


                           
                            //
                            // Here `read_size' and the buffer user
                            // data, that contain a buffer ID#,
                            // is equal to the number of the total
                            // buffers - 1. This means that this is the
                            // _LAST_ little buffer that has been played
                            // by the audio driver. We can STOP the
                            // `wave_out' object now, or restart the
                            // sound playing, if we have a infinite loop.
                            //


                            _this->stop();
                            
                            //
                            // Let the thread go to sleep.
                            //

                            if ( _this->audio_buf.play_finished )
                                _this->audio_buf.play_finished();
                            

                            if ( _this->wakeup_playthread )
                                WaitForSingleObject(_this->wakeup_playthread,
                                                    INFINITE);

                        }  //if ( phdr->dwUser == ( _this->buffers - 1 ))

                    }  //if read_size != 0

                }  //( phdr->dwFlags & WHDR_DONE )
                    

                break; // end case



            case MM_WOM_CLOSE:

                //
                // The thread can exit now.
                //

                return 0;

                break;


            case MM_WOM_OPEN:
                
                //
                // Do nothing.
                //

                break;


        }  //end switch( msg.message )
        
    }  //end while( GetMessage( ... ))

    return 0;				
}


_AUDIO_NAMESPACE_END_
